package com.jxpanda.spring.module.auth.config.properties;

import com.jxpanda.spring.module.auth.core.user.OAuth2ReactiveUserDetailsRepository;
import com.jxpanda.spring.module.auth.endpoint.GrantType;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Getter
@Setter
@ConfigurationProperties(prefix = "panda.spring.auth")
@AutoConfigureAfter(OAuth2ClientProperties.class)
public class AuthConfigProperties {

    /**
     * 开放接口地址
     */
    private List<String> openUrls = new ArrayList<>();

    /**
     * 是否启用本地客户端
     */
    private boolean enableLocalRegistration = true;

    /**
     * 本地客户端的id，默认是local
     */
    @Value("${panda.spring.auth.local-registration-id:${spring.application.name:local}}")
    private String localRegistrationId;

    /**
     * 客户端信息
     */
    private Map<String, RegistrationProperties> registration = Map.of();

    @Autowired
    @Setter(AccessLevel.NONE)
    private EndpointProperties endpointProperties;

    @Autowired
    @Setter(AccessLevel.NONE)
    private OAuth2ClientProperties oAuth2ClientProperties;

    @Autowired
    @Setter(AccessLevel.NONE)
    private Environment environment;

    @Setter(AccessLevel.NONE)
    private List<ClientRegistration> clientRegistrations;

    public synchronized List<ClientRegistration> getClientRegistrations() {
        if (clientRegistrations == null) {
            clientRegistrations = mergeClientRegistration();
        }
        return clientRegistrations;
    }

    private List<ClientRegistration> mergeClientRegistration() {
        Map<String, ClientRegistration> registrationMap = new HashMap<>();
        if (oAuth2ClientProperties != null
            && oAuth2ClientProperties.getRegistration() != null
            && !oAuth2ClientProperties.getRegistration().isEmpty()) {
            registrationMap.putAll(new OAuth2ClientPropertiesMapper(oAuth2ClientProperties).asClientRegistrations());
        }
        this.getRegistration().forEach((registrationId, registration) ->
                registrationMap.computeIfAbsent(registrationId, rid -> {
                    ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(rid)
                            .clientId(registration.getClientId())
                            .clientSecret(registration.getClientSecret())
                            .userNameAttributeName(registration.getUserNameAttributeName())
                            .providerConfigurationMetadata(Map.of(RegistrationProperties.REPOSITORY_BEAN_NAME_KEY, registration.getRepositoryBeanName()))
                            .authorizationGrantType(new AuthorizationGrantType(registration.getAuthorizationGrantType()));
                    if (GrantType.PASSWORD.getValue().equals(registration.getAuthorizationGrantType())) {
                        builder.tokenUri(endpointProperties.getOauthToken());
                    }
                    return builder.build();
                })
        );
        if (this.isEnableLocalRegistration()) {
            registrationMap.put(this.getLocalRegistrationId(), this.getLocalClientRegistration());
        }
        bindWechatMiniProgramProperties(registrationMap);
        return new ArrayList<>(registrationMap.values());
    }

    private void bindWechatMiniProgramProperties(Map<String, ClientRegistration> registrationMap) {
        Map<String, Map<String, String>> wechatMiniProgramProperties = bindWechatMiniProgramProperties();
        if (!wechatMiniProgramProperties.isEmpty()) {
            wechatMiniProgramProperties.forEach((registrationId, properties) -> {
                registrationMap.computeIfAbsent(registrationId, rid ->
                        ClientRegistration.withRegistrationId(rid)
                                .clientId(properties.get("app-id"))
                                .clientSecret(properties.get("app-secret"))
                                .authorizationGrantType(GrantType.WECHAT_MINI_PROGRAM.toAuthorizationGrantType())
                                .build());
            });
        }
    }

    private Map<String, Map<String, String>> bindWechatMiniProgramProperties() {
        Binder evnironmentBinder = Binder.get(environment);
        ResolvableType resolvableType = ResolvableType.forClassWithGenerics(
                Map.class,
                ResolvableType.forClass(String.class),
                ResolvableType.forClassWithGenerics(
                        Map.class,
                        ResolvableType.forClass(String.class),
                        ResolvableType.forClass(String.class)
                )
        );
        Bindable<Map<String, Map<String, String>>> objectBindable = Bindable.of(resolvableType);
        return evnironmentBinder.bind("panda.spring.wechat.mini-program.applets", objectBindable)
                .orElse(new HashMap<>());
    }

    private ClientRegistration getLocalClientRegistration() {
        return ClientRegistration.withRegistrationId(this.getLocalRegistrationId())
                .clientId(this.getLocalRegistrationId())
                .clientSecret(this.getLocalRegistrationId())
                .tokenUri(endpointProperties.getOauthToken())
                .authorizationGrantType(GrantType.PASSWORD.toAuthorizationGrantType())
                .build();
    }

}
